#!/usr/bin/env ruby

# ---------------------------------------------------------------------------- #
# Copyright 2002-2023, OpenNebula Project, OpenNebula Systems                  #
#                                                                              #
# Licensed under the Apache License, Version 2.0 (the "License"); you may      #
# not use this file except in compliance with the License. You may obtain      #
# a copy of the License at                                                     #
#                                                                              #
# http://www.apache.org/licenses/LICENSE-2.0                                   #
#                                                                              #
# Unless required by applicable law or agreed to in writing, software          #
# distributed under the License is distributed on an "AS IS" BASIS,            #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     #
# See the License for the specific language governing permissions and          #
# limitations under the License.                                               #
# ---------------------------------------------------------------------------- #

ONE_LOCATION ||= ENV['ONE_LOCATION']

if !ONE_LOCATION
    RUBY_LIB_LOCATION ||= '/usr/lib/one/ruby'
    GEMS_LOCATION     ||= '/usr/share/one/gems'
else
    RUBY_LIB_LOCATION ||= ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     ||= ONE_LOCATION + '/share/gems'
end

# %%RUBYGEMS_SETUP_BEGIN%%
if File.directory?(GEMS_LOCATION)
    real_gems_path = File.realpath(GEMS_LOCATION)
    if !defined?(Gem) || Gem.path != [real_gems_path]
        $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }

        # Suppress warnings from Rubygems
        # https://github.com/OpenNebula/one/issues/5379
        begin
            verb = $VERBOSE
            $VERBOSE = nil
            require 'rubygems'
            Gem.use_paths(real_gems_path)
        ensure
            $VERBOSE = verb
        end
    end
end
# %%RUBYGEMS_SETUP_END%%

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << File.dirname(__FILE__)

require 'vcenter_driver'

CONFIG = VCenterConf.new

dfile        = ARGV[0]
cluster_name = ARGV[1]
vm_id        = ARGV[2]

## Helpers

def deploy_id_valid?(deploy_id)
    deploy_id && !deploy_id.empty?
end

drv_action = OpenNebula::XMLElement.new
drv_action.initialize_xml(Base64.decode64(STDIN.read), 'VM')

deploy_id     = drv_action['DEPLOY_ID']
host_id       = drv_action['HISTORY_RECORDS/HISTORY[last()]/HID']
deploy        = {}
deploy[:boot] = drv_action['TEMPLATE/OS/BOOT'] \
                                     unless drv_action['TEMPLATE/OS/BOOT'].nil?

begin
    retries ||= 0
    vi_client = VCenterDriver::VIClient.new_from_host(host_id)
    vc_uuid = vi_client.vim.serviceContent.about.instanceUuid
    one_vm = VCenterDriver::VIHelper.one_item(OpenNebula::VirtualMachine, vm_id)

    if !drv_action.retrieve_xmlelements('USER_TEMPLATE/VCENTER_TAG').empty? &&
       !REST_CLIENT
        raise 'vSphere automation gems are not installed'
    end

    if deploy_id_valid?(deploy_id)
        # VM is not new, we just need to reconfigure it and to power it on
        deploy[:new] = false
        vm = VCenterDriver::VirtualMachine.new_one(vi_client, deploy_id, one_vm)
    else
        deploy[:new] = true
        vm = VCenterDriver::VirtualMachine.new_from_clone(vi_client,
                                                          drv_action,
                                                          vm_id)
        if vm.instantiated_as_persistent?
            oclient = OpenNebula::Client.new

            template_id = vm.one_item['TEMPLATE/TEMPLATE_ID']
            vm_template = OpenNebula::Template.new_with_id(template_id, oclient)

            rc = vm_template.info

            raise if OpenNebula.is_error?(rc)

            vm_template.lock(1)
        end

        if vm.vcenter_tags?
            rest_client = VCenterDriver::RESTClient.new_from_host(host_id)
            rest_client.sync_tags(vm)
        end
    end

    lcm_state = drv_action['LCM_STATE']
    check_valid(lcm_state, 'lcm_state')
    lcm_state_str = OpenNebula::VirtualMachine::LCM_STATE[lcm_state.to_i]

    if lcm_state_str == 'BOOT_UNDEPLOY'
        vm.mark_as_virtual_machine
    end

    if vm.powered_off?
        spec_hash = {}
        spec_hash[:cpuHotAddEnabled] = vm.cpu_hot_add_enabled?
        spec_hash[:memoryHotAddEnabled] = vm.memory_hot_add_enabled?
        spec_hash[:uuid] = drv_action['TEMPLATE/OS/UUID'] \
                                     unless drv_action['TEMPLATE/OS/UUID'].nil?

        firmware = drv_action['TEMPLATE/OS/FIRMWARE']

        unless firmware.nil? || firmware.empty?
            spec_hash[:firmware] = drv_action['TEMPLATE/OS/FIRMWARE']
        end

        spec = RbVmomi::VIM.VirtualMachineConfigSpec(
            spec_hash
        )
        vm.item.ReconfigVM_Task(
            :spec => spec
        ).wait_for_completion

        vm.reference_all_disks
        vm.sync(deploy)
        # Only mark the VM as running if we are deploying it for the first time
        set_running = !deploy_id_valid?(deploy_id)
        vm.poweron(set_running)
    end

    puts vm['_ref'] + '_' + vc_uuid
rescue StandardError => e
    message =  "Deploy of VM #{vm_id} on vCenter cluster #{cluster_name} " \
               "with #{dfile} failed due to \"#{e.message}\"" \
               "on attempt \##{retries}."

    if (retries += 1) > CONFIG[:retries] &&
       e.message.include?('ManagedObjectNotFound')
        template_id = drv_action['TEMPLATE/TEMPLATE_ID'].to_i

        client = OpenNebula::Client.new
        resource = OpenNebula::Template.new_with_id(template_id, client)
        rc = resource.info

        if OpenNebula.is_error?(rc)
            message << "\n#{rc.message}"
        else
            template_name = resource.name

            message << "\nVerify inside vCenter cluster #{cluster_name} " \
                       "that template #{template_name} currently " \
                       'exists and has not been deleted'
        end
    end
    OpenNebula.log_error(message)
    if VCenterDriver::CONFIG[:debug_information]
        STDERR.puts "#{message} #{e.backtrace}"
    end
    sleep CONFIG[:retry_interval].to_i
    retry if retries < CONFIG[:retries]

    exit(-1)
ensure
    vi_client.close_connection if vi_client
end
